#include <iostream>
#include <set>
#include <string>
#include <vector>
#include <cstdio>
#include <SDL/SDL.h>
#include <SDL/SDL_image.h>

using namespace std;

SDL_Surface *display;

/* Surface must be locked! */
string block_as_string(SDL_Surface *image, int twidth, int theight, int x, int y) {
  int pitch = image->pitch;
  char *pixels = static_cast<char *>(image->pixels);
  string str(twidth * theight, ' ');

  for(int i = 0; i < theight; ++i) {
    for(int j = 0; j < twidth; ++j) {
      str[j + i * twidth] = pixels[x + j + (y + i) * pitch];
    }
  }
  return str;
}

void display_all_tiles(const set<string> &tiles, SDL_Surface *image, int twidth, int theight) {
  int x = 0;
  int y = 0;
  int pitch = image->pitch;
  char *pixels = static_cast<char *>(image->pixels);

  SDL_LockSurface(image);
  for(set<string>::const_iterator it = tiles.begin(); it != tiles.end(); ++it) {
    string str(*it);
    
    for(int i = 0; i < theight; ++i) {
      for(int j = 0; j < twidth; ++j) {
	pixels[x + j + (y + i) * pitch] = str[j + i * twidth];
      }
    }
    x += twidth;
    if(x >= image->w) {
      x = 0;
      y += theight;
    }
    //printf("%d\t%d\n", x, y);
    if(y >= image->h) break;
  }
  SDL_UnlockSurface(image);
  SDL_BlitSurface(image, NULL, display, NULL);
  SDL_Flip(display);
}

set<string> tile_surface(SDL_Surface *image, int twidth, int theight) {
  SDL_Rect rect = { 0, 0, twidth, theight };
  set<string> tiles;

  SDL_LockSurface(image);
  for(int y = 0; y < image->h / theight; ++y) {
    for(int x = 0; x < image->w / twidth; ++x) {
      rect.x = x * twidth;
      rect.y = y * twidth;
      string ts(block_as_string(image, twidth, theight, rect.x, rect.y));
      if(tiles.find(ts) == tiles.end()) { //New
	tiles.insert(ts);
	SDL_FillRect(display, &rect, SDL_MapRGB(display->format, 0xff, 0, 0));
      } else { //Old
	SDL_FillRect(display, &rect, SDL_MapRGB(display->format, 0, 0xff, 0));
      }
      SDL_Flip(display);
    }
  }
  SDL_UnlockSurface(image);
  SDL_SaveBMP(display, "tiled_structure.bmp");
  return tiles;
}

SDL_Surface *create_tiled_surface(SDL_Palette *palette, const vector<string> &tiles, int twidth, int theight) {
  SDL_Surface *surf;

  surf = SDL_CreateRGBSurface(SDL_SWSURFACE, twidth, theight * tiles.size(), 8, 0, 0, 0, 0);
  if(surf) {
    char *pixels = static_cast<char *>(surf->pixels);
    int pitch = surf->pitch;

    //SDL_SetPalette(surf, SDL_PHYSPAL, palette->colors, 0, palette->ncolors);
    SDL_SetPalette(surf, SDL_LOGPAL, palette->colors, 0, palette->ncolors);
    SDL_LockSurface(surf);
    for(int tile = 0; tile < tiles.size(); ++tile) {
      for(int i = 0; i < theight; ++i) {
	for(int j = 0; j < twidth; ++j) {
	  pixels[j + (i + tile * theight) * pitch] = tiles[tile][j + i * twidth];
	  //pixels[j + (i + tile * theight) * pitch] = 3;
	}
      }
    }
    SDL_UnlockSurface(surf);
  }
  return surf;
}

int main(int argc, char **argv) {
  int twidth, theight;
  SDL_Surface *image;
  SDL_Surface *results;

  if(argc != 4) {
    cerr << "Usage: autotiler twidth theight filename\n";
    return 1;
  }
  twidth = atoi(argv[1]);
  theight = atoi(argv[2]);
  printf("twidth = %d, theight = %d\n", twidth, theight);
  if(twidth < 1 || theight < 1) {
    cerr << "Illegal tile width or height!\n";
    return 2;
  }
  if(SDL_Init(SDL_INIT_VIDEO) == -1) {
    cerr << "Can not initialise SDL!\n";
    return 3;
  }
  if((image = IMG_Load(argv[3])) == NULL) {
    cerr << "Error loading image: " << IMG_GetError() << endl;
    return 4;
  }
  printf("Image loaded with width %d, height %d, and bpp %d.\n", image->w, image->h, image->format->BitsPerPixel);
  if(image->format->BitsPerPixel != 8) {
    cerr << "I can only handle 8 bpp!\n";
    return 5;
  }
  int dw = image->w > 800 ? 800 : image->w;
  int dh = image->h > 400 ? 400 : image->h;
  display = SDL_SetVideoMode(dw, dh, 32, SDL_DOUBLEBUF);
  SDL_BlitSurface(image, NULL, display, NULL);
  SDL_Flip(display);
  set<string> tiles(tile_surface(image, twidth, theight));
  cout << "Number of tiles is " << tiles.size() << ".\n";
  SDL_Delay(1000);
  display_all_tiles(tiles, image, twidth, theight);
  SDL_Delay(2000);
  vector<string> tilevector(tiles.begin(), tiles.end());
  results = create_tiled_surface(image->format->palette, tilevector, twidth, theight);
  if(results) {
    SDL_SaveBMP(results, "tiled_surface.bmp");
    SDL_BlitSurface(results, NULL, display, NULL);
    SDL_Flip(display);
    SDL_Delay(1000);
    SDL_FreeSurface(results);
  } else cerr << "Can not create result surface!\n";
  SDL_FreeSurface(image);
  SDL_Quit();
  return 0;
}
